Skip to content

test(plugin-hono-server): pin the LiteKernel half of the UI auto-discovery guards - #16725

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-16599-hono-ui-discovery-unreachable
Sep 8, 2026
Merged

test(plugin-hono-server): pin the LiteKernel half of the UI auto-discovery guards#16725
os-zhuang merged 1 commit into
mainfrom
claude/issue-16599-hono-ui-discovery-unreachable

Conversation

@os-trump

@os-trump os-trump commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16599

What changed, and why it is not what the card asked for

The card asked for two "unreachable" branches in the hono UI auto-discovery block to be deleted. The dispatch required the premise to be checked first. It was checked per entry point, and it is false for the second published kernel — so nothing was deleted, the reading went back to the seat, and the card was re-scoped (issue comment 5577709701) to what this PR does instead: correct the prose that called those branches dead, and pin the measurement so the tree carries it.

No runtime change. One file — packages/plugins/plugin-hono-server/src/ui-plugin-auto-discovery.pin.test.ts — comments and test cases only.

The measurement, per kernel

@objectstack/core publishes two kernels and the auto-discovery block reads ctx.getKernel().plugins on whichever one is hosting it. They do not agree about this block's inputs:

Entry point type: 'ui' with no slug / no staticPath Why
ObjectKernel.use() refused at boot runs PluginLoader.loadPluginvalidatePluginContractPluginSchema.safeParse; #16334 made both keys required for type: 'ui', #16049/#16363 put the schema on the path
LiteKernel.use() stored verbatim, block runs, both branches execute calls registerPluginByName directly and never reaches PluginLoadergit show --stat 2025b1f7e (#16363) touches no lite-kernel.ts

So the contract change the card names, #16334, narrowed one entry point. LiteKernel is exported from the @objectstack/core barrel (packages/core/src/index.ts), and AGENTS.md's Kernel table names it for "Tests (vitest), serverless, edge (Workers)". With zero in-repo type: 'ui' producers, externally authored plugins are the block's only real callers on either kernel.

Entry points checked

  • ObjectKernel.use() — refuses. PLUGIN_CONTRACT_VIOLATION … at 'slug': PLUGIN_UI_REQUIRED_KEY_MISSING.
  • LiteKernel.use() — reaches both branches. Both inputs are ordinary type-legal Plugin values (tsc --noEmit --strict exit 0 with no casts; negative control exit 2, TS2322).
  • Other ObjectKernelBase subclasses — none (git grep 'extends ObjectKernelBase' = 1 hit, lite-kernel.ts:24).
  • Direct mutation of a kernel's plugins map — reaches (private is erased at runtime). Not a supported API; not relied on.
  • A foreign host object from ctx.getKernel() — reaches; that is what the block's Array / Object.values arms are for.
  • Other production getKernel() providers — none beyond the two kernels.

Both branches are load-bearing, not merely reachable

Measured by ablation, and the readings are what justify the new pins:

  • Dropping the || in plugin.slug || plugin.name.split('/').pop() moves the mounted route from /console to /undefined.
  • Dropping the && plugin.staticPath conjunct turns an assetless ui plugin's clean boot into TypeError: The "paths[1]" argument must be of type string. Received undefined, out of path.resolve(process.cwd(), mount.root) further down start().

What this PR adds

Group F, three cases on LiteKernel, turning those one-off ablation readings into pins the tree keeps:

  • F0 — firing control: a fully declared ui plugin mounts on this kernel too. Without it, F2's [] would be indistinguishable from a harness that never mounts here.
  • F1 — no slug: the fallback derives console from @os-fixture/console. The string console appears in no fixture field, only in the tail of name, so nothing but the fallback can satisfy it.
  • F2 — no staticPath: the guard skips the plugin and start() resolves. The second half is the point; the conjunct is the difference between a clean boot and a crashed one.

Falsifier run, from this commit (each leg: anchored mutation, on-disk blob proof, trap restore verified by blob equality with HEAD and an empty git diff HEAD):

ablation result
drop the `
drop && plugin.staticPath F2 red onlyTypeError: The "paths[1]" argument must be of type string. Received undefined; 16 passed, 1 todo

Each leg reddens exactly its own pin, so the two are specific rather than a blanket break.

Three prose corrections, each previously asserting deadness with no case behind it:

  1. pin B's slug-refusal comment — "The fallback expression itself is dead code now, awaiting its own card" → refused on ObjectKernel since spec: PluginSchema makes staticPath / slug really required for type: "ui" (superRefine), and core's Plugin interface derives from PluginDefinition (spec half of #16049) #16334, live and load-bearing on LiteKernel, pinned by F1.
  2. pin D's staticPath-refusal comment — "likewise unreachable through the kernel" → unreachable on that kernel, with F2 naming what deleting the conjunct actually does.
  3. the it.todo case-C narration — "the boot path — which never calls PluginSchema" named no kernel. It is still true for LiteKernel and stopped being true for ObjectKernel at feat(core): enforce PluginSchema at kernel.use() (#16049) #16363.

The file header now states the two-kernel split once, so "reachable" is written as a property of a branch per kernel.

For whoever dispatches #15638

#15638's subject is likewise only half dead, and this PR does not touch it. Its ui-plugin disjunct is refused by ObjectKernel.use() since #16363 (PLUGIN_CONTRACT_VIOLATION … at 'type') but still accepted by LiteKernel.use(), where the block still mounts /slug and /slug/* — measured. The it.todo in this file now says so, and its two candidate pins are spelled against bootLite. Whoever lands that card owes both halves, exactly as this one did. #15638 is not addressed here and remains open.

⛔ Also deliberately not pre-empted here, and carried on the seat's own decision card: whether LiteKernel should enforce PluginSchema at all, and the isDefault alias two lines from this guard. This PR pins only what the tree does today.

Gates

  • dispatch-gates --tier: no path-derived mandate (the surface hits none of the 3 declared globs).
  • dispatch-gates --commands → run → --ran: ✓ dispatch-gates --ran: 47 derived famil(ies) accounted for — 47 run, 0 NOT-MEASURED, 0 unrun. 45 exit 0.
  • Two returned exit 3 with PREREQUISITE NOT MET, which is NOT MEASURED and not a findingcheck:dual-build-cjs-loads ("this gate reads built output, and some package has no dist/") and check:type-check-debt ("⛔ This is NOT a pass and NOT a finding: nothing was measured"). Both demand a full workspace build (turbo run build --filter='./packages/*' …) which CI performs before those steps; this is a declared narrowing, and neither gate's subject can move for this diff, which contributes zero bytes to any dist/.
  • Package: pnpm --filter @objectstack/plugin-hono-server test — 23 files, 264 passed, 1 todo. typecheck — exit 0 (both the build program and tsconfig.test.json read src/**/*, so the changed file is genuinely in the typechecked surface).

Clause ② — derived from the real export surface

No. Mechanically, with firing controls rather than a path test:

  • exported symbols declared in the changed file: 0 (control: hono-plugin.ts = 7 through the same grep).
  • other modules referencing it, barrels included: 0 (control: from './hono-plugin' = 8 through the same grep shape).
  • export lines added or removed by the diff: 0.

Nothing enters or leaves the public surface, and the behaviour reading agrees this round for a stronger reason than last: the diff changes no runtime code at all.

Changeset

skip-changeset, and judged rather than assumed — AGENTS.md:1029 reserves that label for "a diff that publishes nothing from any released package", so the question is whether this file publishes.

  • package.json files: ["dist","README.md","CHANGELOG.md"] — no .npmignore.
  • Root tsup.config.ts entry: ['src/index.ts'], so the bundle cannot reach a test file.
  • Measured against a freshly built dist/, with a firing positive control: a string from the shipped auto-discovery block is present in dist/index.js and dist/index.mjs (2 hits); a string unique to the changed test file is absent (0 hits). An earlier reading taken against an empty dist/ was discarded as NOT MEASURED — its control did not fire.
  • pnpm check:published-files exit 0.

⇒ Nothing ships. Had the card's original deletion gone ahead it would have been the opposite call — a breaking changeset on @objectstack/plugin-hono-server, since for a LiteKernel-hosted ui plugin the FROM/TO would be /lastNameSegment/undefined and "boots clean, mounts nothing" → "crashes start()", plus the ADR-0087 disposition marker.

Evidence

Raw logs for every run above, kept at /tmp/claude-0/-home-user-objectstack/33fa52ce-0dc9-5d1e-aea9-7f2a955fa345/scratchpad/issue-16599/: probe-source-kept.ts (the throwaway reachability probe, 9 cases), probe1.log, probe2.log, ablate2.sh, f-ablate-slug.log, f-ablate-slug-pin.log, f-ablate-sp.log, f-ablate-sp-pin.log, typeprobe.log, typeprobe-neg.log, pkgtest.log, pkgtypecheck.log, g-cmds2.log, g-results.txt, g-recon.log, gate-1.loggate-47.log. Every build and test ran in the foreground through scripts/pm/os-verify-lock.sh; verdicts are read from its printed VERDICT line, never a bare $?.


🤖 Generated with Claude Code

https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37


Generated by Claude Code

…overy guards (#16599)

The pin file asserted, in prose, that the slug-from-name fallback and the
`&& plugin.staticPath` conjunct were dead code once `PluginSchema` began
requiring both keys. That was a claim about every entry point argued from one.
Measured per kernel, it is false for the second published kernel:

  - `ObjectKernel.use()` runs `PluginLoader.validatePluginContract` ->
    `PluginSchema.safeParse` and refuses both inputs (#16334, #16363). The two
    existing refusal pins are correct and keep their expectations.
  - `LiteKernel.use()` calls `registerPluginByName` directly and never reaches
    `PluginLoader`, so both objects are stored verbatim, the block iterates
    them, and both branches execute.

Ablation, from this commit: dropping the `||` moves the mounted route from
`/console` to `/undefined`; dropping the `&& plugin.staticPath` conjunct turns
an assetless `ui` plugin's clean boot into a `TypeError` naming `paths[1]`, out
of `path.resolve(process.cwd(), mount.root)`. Both branches are load-bearing.

New group F carries those two readings as permanent pins, with F0 as the firing
control that the `LiteKernel` harness can mount at all — so F2's `[]` is caused
by the conjunct and not by a harness that never mounts. The three passages that
said otherwise now state the fact per kernel, including the `it.todo` case-C
narration, whose "the boot path — which never calls `PluginSchema`" is still
true for `LiteKernel` and stopped being true for `ObjectKernel` at #16363.

No runtime change: one test file, comments and cases only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json be92d468043c1b9e100e6e0f2c2fc9216a5aa850packageMentionDocs.

@os-zhuang
os-zhuang added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit a8f3faa Sep 8, 2026
39 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-16599-hono-ui-discovery-unreachable branch September 8, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

3 participants